package net.danielseidl.ludumdare.ld48.character;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.logging.Logger;

import javax.imageio.ImageIO;

import net.danielseidl.ludumdare.ld48.Game;
import net.danielseidl.ludumdare.ld48.level.Level;

/**
 * Abstract superclass for characters in the game.
 *
 * @author    Daniel Seidl - info@daniel-seidl.net
 */
public abstract class Character extends Thread
	{
	private Logger LOGGER = Logger.getLogger(Character.class.getName());
	
	/**
	 * The character's location.
	 */
	protected int[] location;
	
	/**
	 * The level instance.
	 */
	protected Level level;
	
	/**
	 * The game instance.
	 */
	protected Game game;	
	
	/**
	 * The character's movement speed.
	 */
	protected int stats_speed;
	
	/**
	 * The character's images.
	 */
	private HashMap<String, BufferedImage> charImages = new HashMap<String, BufferedImage>();
	
	/**
	 * The character's current image.
	 */
	private BufferedImage currentImage;
	
	/**
	 * The character's current direction.
	 */
	private MoveDirection currentDirection;
	
	/**
	 * The character's current speed.
	 */
	private SpeedType currentSpeed;
	
	// Used for the character's walking animation.
	private int moveCounter;
	private int max_move_counter;
	private int counter_div;
	
	/**
	 * Creates a new character with the given data.
	 * @param location The character's position inside the level.
	 * @param level The level instance to use.
	 * @param speed The character's speed type.
	 * @param image The image file to use for this character.
	 */
	public Character(int[] location, Level level, SpeedType speed, String image)
		{
		this.location = location;
		this.level = level;
		
		// Set the character's speed.
		this.setSpeedType(speed);
		
		// Load the character's images.
		this.loadCharImages(image);
		}
	
	/**
	 * Sets the game instance to use.
	 * @param game The game instance to use.
	 */
	public void setGameInstance(Game game)
		{
		this.game = game;
		}
	
	/**
	 * Sets the characters's location in the level.
	 * @param location The new location values.
	 */
	public void setLocation(int[] location)
		{
		this.location = location;
		}	
	
	/**
	 * Sets the current speed for this character.
	 * @param speed The speed type to use.
	 */
	public void setSpeedType(SpeedType speed)
		{
		this.currentSpeed = speed;
		
		switch (this.currentSpeed.ordinal())
			{
			case 0: // slow = 20
				this.stats_speed = 15;
				this.max_move_counter = 14;
				this.counter_div = 5;
				break;
			case 1: // medium = 40
				this.stats_speed = 40;
				this.max_move_counter = 8;
				this.counter_div = 3;
				break;
			case 2: // fast = 80
				this.stats_speed = 80;
				this.max_move_counter = 2;
				this.counter_div = 1;
				break;
			}
		}
	
	/**
	 * Loads and stores the needed images for this character.
	 * @param image
	 */
	protected void loadCharImages(String image)
		{
		try
			{
			File file = new File("gfx/chars/" + image + ".png");
			BufferedImage fullImage = ImageIO.read(file);
			
			// Walking up images.
			this.charImages.put("up-0", fullImage.getSubimage(0, 0, 48, 64));
			this.charImages.put("up-1", fullImage.getSubimage(47, 0, 48, 64));
			this.charImages.put("up-2", fullImage.getSubimage(95, 0, 48, 64));
			
			// Walking right images.
			this.charImages.put("right-0", fullImage.getSubimage(0, 65, 48, 64));
			this.charImages.put("right-1", fullImage.getSubimage(47, 65, 48, 64));
			this.charImages.put("right-2", fullImage.getSubimage(95, 65, 48, 64));
			
			// Walking down images.
			this.charImages.put("down-0", fullImage.getSubimage(0, 129, 48, 64));
			this.charImages.put("down-1", fullImage.getSubimage(47, 129, 48, 64));
			this.charImages.put("down-2", fullImage.getSubimage(95, 129, 48, 64));	
			
			// Walking left images.
			this.charImages.put("left-0", fullImage.getSubimage(0, 192, 48, 64));
			this.charImages.put("left-1", fullImage.getSubimage(47, 192, 48, 64));
			this.charImages.put("left-2", fullImage.getSubimage(95, 192, 48, 64));
			
			// Set the current image.
			this.currentDirection = MoveDirection.DOWN;
			this.stopCharacterImage();
			}
		catch (IOException e)
			{
			this.LOGGER.severe("unable to load critical image 'gfx/chars/" + image + ".png'");
			this.LOGGER.severe("terminating running system...");
			System.exit(1);
			}
		}
	
	/**
	 * Returns the character's location.
	 * @return The character's location.
	 */
	public int[] getLocation()
		{
		return this.location;
		}
	
	/**
	 * Returns the character's current Image.
	 * @return The character's current Image.
	 */
	public BufferedImage getCurrentImage()
		{
		return this.currentImage;
		}
	
	/**
	 * Sets the current character image to a standing image.
	 */
	public void stopCharacterImage()
		{
		this.currentImage = this.charImages.get(this.currentDirection.toString().toLowerCase() + "-1");
		this.moveCounter = 1;
		}
	
	/**
	 * Moves the character on the map using the given speed of the character.
	 * @param md The direction to move to.
	 * @returns true if the entity is now in another tile.
	 */
	public boolean moveCharacter(MoveDirection md)
		{	
		// Change the current direction if necessary.
		if (!this.currentDirection.equals(md))
			{
			this.currentDirection = md;
			this.stopCharacterImage();
			}

		// Change move counter.
		this.moveCounter++;
		if (this.moveCounter > this.max_move_counter)
			{
			this.moveCounter = 0;
			}
		
		this.currentImage = this.charImages.get(this.currentDirection.toString().toLowerCase() + "-" + (int)Math.ceil(this.moveCounter / this.counter_div));
		
		if (md.equals(MoveDirection.LEFT))
			{
			return this.moveLeft();
			}
		else if (md.equals(MoveDirection.RIGHT))
			{
			return this.moveRight();
			}
		else if (md.equals(MoveDirection.UP))
			{
			return this.moveUp();
			}
		else if (md.equals(MoveDirection.DOWN))
			{
			return this.moveDown();
			}
		
		return false;
		}
	
	/**
	 * Moves the character left.
	 * @returns true if the character is now in another tile.
	 */
	private boolean moveLeft()
		{
		boolean ret = false;
		// at speed 30 we can walk 120 pixels per second, which are 4 pixels per frame
		this.location[2] -= (this.stats_speed / 7.5);
		if (this.location[2] < 0)
			{
			ret = true;
			this.location[2] = 32 - this.location[2];
			this.location[0]--;
			}
		this.checkLocation();
		return ret;
		}
	
	/**
	 * Moves the character right.
	 * @returns true if the character is now in another tile.
	 */
	private boolean moveRight()
		{
		boolean ret = false;
		// at speed 30 we can walk 120 pixels per second, which are 4 pixels per frame
		this.location[2] += (this.stats_speed / 7.5);
		if (this.location[2] > 31)
			{
			ret = true;
			this.location[2] = this.location[2] - 32;
			this.location[0]++;
			}
		this.checkLocation();
		return ret;
		}
	
	/**
	 * Moves the character up.
	 * @returns true if the character is now in another tile.
	 */
	private boolean moveUp()
		{
		boolean ret = false;
		// at speed 30 we can walk 120 pixels per second, which are 4 pixels per frame
		this.location[3] -= (this.stats_speed / 7.5);
		if (this.location[3] < 0)
			{
			ret = true;
			this.location[3] = 32 - this.location[3];
			this.location[1]--;
			}
		this.checkLocation();
		return ret;
		}
	
	/**
	 * Moves the character down.
	 * @returns true if the character is now in another tile.
	 */
	private boolean moveDown()
		{
		boolean ret = false;
		// at speed 30 we can walk 120 pixels per second, which are 4 pixels per frame
		this.location[3] += (this.stats_speed / 7.5);
		if (this.location[3] > 31)
			{
			ret = true;
			this.location[3] = this.location[3] - 32;
			this.location[1]++;
			}
		this.checkLocation();
		return ret;
		}	
	
	/**
	 * Checks the character's location to make sure it's still inside the level.
	 */
	private void checkLocation()
		{
		int width = this.level.getLevelWidth() - 1;
		int height = this.level.getLevelHeight() - 2;
		
		if (this.location[0] < 1 && this.location[2] < 15)
			{
			this.location[0] = 0;
			this.location[2] = 15;
			}
		else if (this.location[0] > width - 1 && this.location[2] > 15)
			{
			this.location[0] = width;
			this.location[2] = 15;
			}
		
		if (this.location[1] < 0)
			{
			this.location[1] = 0;
			this.location[3] = 0;
			}
		else if (this.location[1] > height)
			{
			this.location[1] = height;
			this.location[3] = 31;
			}		
		}
	
	/**
	 * Checks if the character is at the given tile.
	 * @param tile The tile to check.
	 * @return true if the character is on this tile.
	 */
	protected boolean isCharAtTile(int[] tile)
		{
		boolean ret = false;
		
		int tileW = tile[0] * 32;
		int tileH = tile[1] * 32;
		
		int playerW = this.location[0] * 32 + this.location[2];
		int playerH = this.location[1] * 32 + this.location[3];
				
		if (playerW >= tileW - 5 && playerW <= tileW + 32
				&& playerH >= tileH - 25 && playerH <= tileH + 8)
			{
			ret = true;
			}
		
		return ret;
		}
	}